// Copyright (c) 2012 Christian Kollross / Michael Freymueller

package de.pixel83.piecemaster;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;

// imcs.svcs.cs.pdx.edu:3589

public class Board {

	private char[][] board = new char[6][5];

	private int moveNum = -1;

	private char onMove = '?';

	private String ls = "\n"; // line separator

	public enum MoveResult {
		OK, ILLEGAL, DRAW, WIN_W, WIN_B
	}

	public Board() {
		String intialSetup = "1 W" + ls + "kqbnr" + ls + "ppppp" + ls + "....."
				+ ls + "....." + ls + "PPPPP" + ls + "RNBQK";
		setBoard(intialSetup);
	}

	public Board(String input) {
		setBoard(input);
	}

	public char getPieceAt(int row, int col) {
		return board[row][col];
	}

	public int getMoveNum() {
		return moveNum;
	}

	public char getColor() {
		return onMove;
	}

	public void setColor(char color) {
		this.onMove = color;
	}

	public void setBoard(String input) {
		String[] lines = input.split(ls);
		assert (lines.length == 7);

		// Process first line
		String firstLine = lines[0];
		String moveNumText = firstLine.split(" ")[0];
		moveNum = Integer.parseInt(moveNumText);
		for (int i = 0; i < firstLine.length(); i++) {
			if (firstLine.charAt(i) == 'W' || firstLine.charAt(i) == 'B')
				onMove = firstLine.charAt(i);
		}

		// Process board
		String line;
		for (int l = 1; l < 7; l++) {
			line = lines[l];
			for (int i = 0; i < 5; i++) {
				this.board[6 - l][i] = line.charAt(i);
			}
		}
		assert (moveNum > 0 && moveNum <= 40);
		assert (onMove == 'B' || onMove == 'W');
	}

	public Board(BufferedReader br) throws IOException {
		String read = br.readLine();

		while (read != null) {
			System.out.println(read);
			read = br.readLine();
		}
		setBoard(read);
	}

	public String toString() {
		String output = moveNum + " " + onMove + ls;
		for (int l = 5; l >= 0; l--) {
			for (int i = 0; i < 5; i++) {
				output += (char) this.board[l][i];
			}
			output += ls;
		}
		return output;
	}

	public void print(OutputStream os) {
		BufferedWriter bw = new BufferedWriter(new PrintWriter(os));

		try {
			bw.write(this.toString());
			bw.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * Tries to execute a move
	 * 
	 * @param mov
	 * @return
	 */
	public MoveResult move(Move mov) {
		assert (mov != null);
		MoveResult result = MoveResult.OK;

		if (Board.getColor(board[mov.from.row][mov.from.col]) == onMove) {

			ArrayList<Move> legalMoves = getLegalMoves(mov.from);
			for (Move m : legalMoves) {
				if (m.equals(mov)) {
					char replacedPiece = board[mov.to.row][mov.to.col];

					// do the move
					board[mov.to.row][mov.to.col] = board[mov.from.row][mov.from.col];

					// Convert P to Q in last opposing row
					char piece = board[mov.to.row][mov.to.col];
					if (piece == 'P' && mov.to.row == 5) {
						board[mov.to.row][mov.to.col] = 'Q';
					} else if (piece == 'p' && mov.to.row == 0) {
						board[mov.to.row][mov.to.col] = 'q';
					}

					// Check if one side has won
					if (piece != '.') {
						if (replacedPiece == 'K') {
							result = MoveResult.WIN_B;
						} else if (replacedPiece == 'k') {
							result = MoveResult.WIN_W;
						}
					}

					if (moveNum >= 40) {
						result = MoveResult.DRAW;
					}

					board[mov.from.row][mov.from.col] = '.';
					if (onMove == 'W') // change the color
						onMove = 'B';
					else {
						onMove = 'W';
						moveNum++; // increase move number
					}
				}
			}
		} else {
			System.out.println("Falsche Farbe");
			result = MoveResult.ILLEGAL;
		}
		return (result);
	}

	/**
	 * Constructor expecting a String like "b2-b3"
	 * 
	 * @param move
	 * @return
	 */
	public MoveResult move(String move) {
		assert (move != null);
		Move mov = new Move(move);
		return this.move(mov);
	}

	/**
	 * Returns an ArrayList of Moves that can be performed legally from the
	 * giben Square.
	 * 
	 * @param from
	 * @return
	 */
	public ArrayList<Move> getLegalMoves(Square from) {
		ArrayList<Move> legalMoves = new ArrayList<Move>();
		boolean captureAllow = true;
		boolean requireCapture = false;
		boolean shortStop = false;
		int dx = 0;
		int dy = 0;
		int cache = 0;
		char piece = ' ';
		if (board[from.row][from.col] != '.')
			piece = Character.toLowerCase(board[from.row][from.col]);
		else
			return new ArrayList<Move>();
		switch (piece) {
		case 'k':
			shortStop = true;
		case 'q':
			for (dx = -1; dx <= 1; dx++)
				for (dy = -1; dy <= 1; dy++)
					if (!(dx == 0 && dy == 0))
						scan(legalMoves, from, dx, dy, shortStop, captureAllow);
			break;
		case 'b': // bishop parameter setzen
			shortStop = true;
			captureAllow = false;
		case 'r':

			dx = 1;
			dy = 0;
			cache = 0;
			for (int i = 0; i < 4; i++) { // Turm bewegungen und falls bishop
											// seine hoch, runter... züge
				scan(legalMoves, from, dx, dy, shortStop, captureAllow);
				cache = dx;
				dx = dy;
				dy = cache;
				dy = dy * -1;
			}
			if (piece == 'b') { // diagonalen des bishop
				dx = 1;
				dy = 1;
				shortStop = false;
				captureAllow = true;
				for (int i = 0; i < 4; i++) {
					scan(legalMoves, from, dx, dy, shortStop, captureAllow,
							requireCapture);
					cache = dx;
					dx = dy;
					dy = cache;
					dy = dy * -1;
				}
			}
			break;
		case 'n': // pferd
			shortStop = true;
			dx = 1;
			dy = 2;
			cache = 0;
			for (int i = 0; i < 4; i++) {
				scan(legalMoves, from, dx, dy, shortStop, captureAllow);
				cache = dx;
				dx = dy;
				dy = cache;
				dy = dy * -1;
			}
			dx = -1;
			dy = 2;
			for (int i = 0; i < 4; i++) {
				scan(legalMoves, from, dx, dy, shortStop, captureAllow);
				cache = dx;
				dx = dy;
				dy = cache;
				dy = dy * -1;
			}
			break;
		case 'p': // Bauer
			int dir = 1;
			if (Character.isLowerCase(board[from.row][from.col]))
				dir = -1;
			shortStop = true;
			captureAllow = true;
			requireCapture = true;
			scan(legalMoves, from, -1, dir, shortStop, captureAllow,
					requireCapture);
			scan(legalMoves, from, +1, dir, shortStop, captureAllow,
					requireCapture);
			captureAllow = false;
			scan(legalMoves, from, 0, dir, shortStop, captureAllow);
		}
		return legalMoves;
	}

	/**
	 * Wrapper for scan() assuming requireCapture == false
	 * 
	 * @param legalMoves
	 * @param from
	 * @param dc
	 * @param dr
	 * @param shortStop
	 * @param allowCapture
	 */
	private void scan(ArrayList<Move> legalMoves, Square from, int dc, int dr,
			boolean shortStop, boolean capture) {
		scan(legalMoves, from, dc, dr, shortStop, capture, false);
	}

	/**
	 * Scans for legal moves in a certain direction.
	 * 
	 * @param legalMoves
	 * @param dc
	 * @param dr
	 * @param shortStop
	 * @param allowCapture
	 * @param requireCapture
	 */
	private void scan(ArrayList<Move> legalMoves, Square from, int dc, int dr,
			boolean shortStop, boolean allowCapture, boolean requireCapture) {
		assert (requireCapture ? allowCapture : true);

		int row = from.row;
		int col = from.col;

		boolean stopped = false; // true when captured, blocked or left board
		while (!stopped) {
			row += dr;
			col += dc;
			if (isOnBoard(row, col)) {
				if (!requireCapture && board[row][col] == '.') {
					// Target is empty
					legalMoves.add(new Move(from.toString() + "-"
							+ new Square(col, row).toString()));
				} else if (allowCapture && onMove != getColor(board[row][col])
						&& board[row][col] != '.') {
					// Target may be captured
					stopped = true;
					legalMoves.add(new Move(from.toString() + "-"
							+ new Square(col, row).toString()));
				} else {
					stopped = true; // neither captured nor empty
				}
			} else {
				stopped = true; // left the board
			}
			if (shortStop)
				stopped = true;
		}
	}

	/**
	 * Returns of the given coordinates are valid (= within the board)
	 * 
	 * @param row
	 * @param col
	 * @return
	 */
	public boolean isOnBoard(int row, int col) {
		return (row >= 0 && row <= 5 && col >= 0 && col <= 4);
	}

	/**
	 * Returns the color ('W'/'B') for the given piece.
	 * 
	 * @param piece
	 * @return
	 * @return
	 */
	public static char getColor(char piece) {
		if (piece > 'a' && piece <= 'z')
			return 'B';
		else if (piece >= 'A' && piece <= 'Z')
			return 'W';
		else
			return '.';
	}

	public static char invertColor(char color) {
		assert (color == 'W' || color == 'B');
		return (color == 'W' ? 'B' : 'W');
	}

	public static void main(String[] args) {
		System.out.println("Testing Board...");

		Board board = new Board();
		ArrayList<Move> moves = new ArrayList<Move>();

		board.print(System.out); // testet print methode
		// System.out.println("einige Testzüge");
		// System.out.println(bo.toString());
		System.out.println("teste Zug a2-a3");
		board.move("a2-a3");
		board.print(System.out);
		System.out.println("teste Zug a5-a4");
		board.move("a5-a4");
		board.print(System.out);

		System.out.println("other tests");
		// System.out.println(bo.toString());
		// System.out.println("this shoud do an error (2 times same color)");
		// bo.move("a3-a4");

		board.scan(moves, new Square("b2"), 1, 1, false, true);
		assert (moves.size() == 3);

		System.out.println("Testing Board class ...");
		board = new Board();

		// board.print(System.out); // testet print methode
		// System.out.println("einige Testzüge");
		// System.out.println(bo.toString());
		board.move("a2-a3");
		// System.out.println(bo.toString());
		// System.out.println("this shoud do an error (2 times same color)");
		// bo.move("a3-a4");

		System.out.println("Testing isOnBoard() ...");
		assert (board.isOnBoard(0, 0));
		assert (board.isOnBoard(0, 0));

		System.out.println("Testing getColor() ...");
		assert ('B' == board.getColor('q'));
		assert ('W' == board.getColor('K'));
		assert ('.' == board.getColor('.'));

		System.out.println("Testing scan() ...");
		board = new Board();
		moves = new ArrayList<Move>();
		board.scan(moves, new Square("b2"), 1, 1, false, true);
		assert (moves.size() == 3);
		assert ("b2-c3".equals(moves.get(0).toString()));
		assert ("b2-d4".equals(moves.get(1).toString()));
		assert ("b2-e5".equals(moves.get(2).toString()));

		// Test move blocked by own piece
		board = new Board();
		moves = new ArrayList<Move>();
		board.scan(moves, new Square("a6"), 0, -1, false, true);
		assert (moves.size() == 0);

		// Test shortStop
		board = new Board();
		moves = new ArrayList<Move>();
		board.scan(moves, new Square("e2"), 0, 1, true, false);
		assert (moves.size() == 1);
		assert ("e2-e3".equals(moves.get(0).toString()));

		// Test stop by board frame
		board = new Board();
		moves = new ArrayList<Move>();
		board.scan(moves, new Square("c2"), -1, 1, false, false);
		assert (moves.size() == 2);
		assert ("c2-b3".equals(moves.get(0).toString()));
		assert ("c2-a4".equals(moves.get(1).toString()));

		// Test stop by non-beatable opponent piece
		board = new Board();
		moves = new ArrayList<Move>();
		board.scan(moves, new Square("a2"), 0, 1, false, false);
		assert (moves.size() == 2);
		assert ("a2-a3".equals(moves.get(0).toString()));
		assert ("a2-a4".equals(moves.get(1).toString()));

		// Test stop by lack of beatable opponent piece
		board = new Board();
		moves = new ArrayList<Move>();
		board.scan(moves, new Square("d2"), 0, 1, false, true, true);
		assert (moves.size() == 0);

		System.out.println("SUCCESS - All Board tests passed.");
	}

}
